#include "tbcore/base/memory_buffer.hpp"

#include <boost/move/move.hpp>

#include "tbcore/base/logging.hpp"
#include "tbcore/base/assert.hpp"

#include "tbcore/base/buffer_impl.hpp"

TB_NAMESPACE_BEGIN

MemoryBuffer::MemoryBuffer()
  :impl_(new MemoryBufferImpl()) {}

MemoryBuffer::MemoryBuffer( const std::string& data )
  : impl_(new MemoryBufferImpl()) {
  WriteBytes(data.data(), (uint32)data.length());
}

MemoryBuffer::MemoryBuffer( const char* data, uint32 len )
  : impl_(new MemoryBufferImpl()) {
  WriteBytes(data, len);
}

MemoryBuffer::~MemoryBuffer() {
	TB_SAFE_DELETE(impl_);
}

Blob MemoryBuffer::Merge() const {
  return merge(impl_->allocator_, impl_->blobs_.begin(), impl_->blobs_.end());
}

void MemoryBuffer::Swap( MemoryBuffer& buf ) {
  std::swap(buf.impl_, this->impl_);
}

uint32 MemoryBuffer::Size() const {
  return impl_->InternalSize();
}

bool MemoryBuffer::SeekWrite(uint32 pos ) {
  if (pos > impl_->readIndex_) {
    //support foward/backard operation
    Reserve(pos);

    uint32 writtingBlob = 0;
    uint32 offset = pos;
    
    impl_->CalBlobIndexAndOffset(pos, writtingBlob, offset);

    if (writtingBlob > 0 && writtingBlob <= impl_->blobs_.size() && writtingBlob >= impl_->readingBlob_) {
      impl_->writeIndex_ = pos;
      impl_->writingBlob_ = writtingBlob;
      impl_->writeOffset_ = offset;
      return true;
    }
  } else {
    LWARNING() << "seek pos must greater or equal to read index";
  }
  return false;
}

void MemoryBuffer::WriteBytes( const char* data, uint32 len ) {
  impl_->InternalCheck();

	impl_->InternalResize(impl_->writeIndex_ + len, false);

	uint32 dataOffset = 0;
	while (dataOffset < len) {
		uint32 availableSize = impl_->blobCapacity_ - impl_->writeOffset_;
		if (availableSize > 0) {
			uint32 writeSize = std::min(availableSize, len - dataOffset);
			Blob& b = impl_->BlobAt(impl_->writingBlob_ - 1);
			memcpy(const_cast<char*>(b.content()) + impl_->writeOffset_, data + dataOffset, writeSize);
			dataOffset += writeSize;
			impl_->writeIndex_ += writeSize;
			impl_->writeOffset_ += writeSize;
			if (impl_->writeOffset_ > b.size()) {
				b.setSize(impl_->writeOffset_);
			}
		} else {
			impl_->writingBlob_ += 1;
			impl_->writeOffset_ = 0;
		}
	}
}

void MemoryBuffer::WriteBytes(MemoryBuffer& buf, uint32 len) {
  if (buf.Size() < len) {
    _LWARNING() << "WriteBytes: buffer size is less than write length!";
    return;
  }

  impl_->InternalCheck();

  impl_->InternalResize(impl_->writeIndex_ + len, false);

  uint32 dataOffset = 0;
  while (dataOffset < len) {
    uint32 availableSize = impl_->blobCapacity_ - impl_->writeOffset_;
    if (availableSize > 0) {
      uint32 writeSize = std::min(availableSize, len - dataOffset);
      Blob& b = impl_->BlobAt(impl_->writingBlob_ - 1);
      buf.ReadBytes(writeSize, const_cast<char*>(b.content()) + impl_->writeOffset_);
      dataOffset += writeSize;
      impl_->writeIndex_ += writeSize;
      impl_->writeOffset_ += writeSize;
      if (impl_->writeOffset_ > b.size()) {
        b.setSize(impl_->writeOffset_);
      }
    } else {
      impl_->writingBlob_ += 1;
      impl_->writeOffset_ = 0;
    }
  }
}

bool MemoryBuffer::SeekRead(uint32 pos)
{
	if (pos > impl_->writeIndex_) {
		return false;
	}

	uint32 readingBlob = 0;
	uint32 offset = pos;

	impl_->CalBlobIndexAndOffset(pos, readingBlob, offset);

	if (readingBlob > 0 && readingBlob <= impl_->blobs_.size() && readingBlob <= impl_->writingBlob_) {
		impl_->readIndex_ = pos;
		impl_->readingBlob_ = readingBlob;
		impl_->readOffset_ = offset;
		return true;
	}

	return false;
}

const char* MemoryBuffer::ReadValue(uint32 len) {
	tuple<const char*, uint32> d = impl_->InternalReadValue(len, true);
	return get<1>(d) == len ? get<0>(d) : nullptr;
}

tuple<const char*, uint32> MemoryBuffer::PeekBytes(uint32 len) {
	return impl_->InternalReadValue(len, false);
}

uint32 MemoryBuffer::ReadIndex() const {
	return impl_->readIndex_;
}

uint32 MemoryBuffer::WriteIndex() const {
	return impl_->writeIndex_;
}

void MemoryBuffer::Shrink() {
	uint32 blobIndex;
	uint32 blobOffset;
	impl_->CalBlobIndexAndOffset(impl_->readIndex_, blobIndex, blobOffset);

	if (blobIndex > 0 && blobIndex <= impl_->blobs_.size() && blobIndex <= impl_->writingBlob_) {
		MemoryBufferImpl::Blobs_T::iterator begin = impl_->blobs_.begin();
		if (blobOffset == impl_->BlobAt(blobIndex).size()) {
			blobIndex++;
			blobOffset = 0;
			impl_->readingBlob_++;
			impl_->readOffset_ = 0;
			impl_->writingBlob_--;
		}

		for (uint32 i = 1; i < blobIndex; ++i) {
			begin = impl_->blobs_.begin();
			impl_->blobs_.push_back(boost::move(*begin));
			impl_->blobs_.erase(begin);
		}

		impl_->writeIndex_ -= impl_->readIndex_ - blobOffset;
		impl_->readIndex_ = blobOffset;
		impl_->writingBlob_ -= blobIndex - impl_->readingBlob_;
		impl_->readingBlob_ = 1;
	}
}

void MemoryBuffer::SkipRead(uint32 len) {
	SeekRead(impl_->readIndex_ + len);
}

void MemoryBuffer::Reserve(uint32 len) {
	impl_->InternalResize(len, false);
}

void MemoryBuffer::WriteValue(const char* data, uint32 len) {
	if (len > impl_->blobCapacity_) {
		LERROR() << "can't write value who's sizeof > blob capacity!";
		return;
	}
  impl_->InternalCheck();

	impl_->InternalResize(impl_->writeIndex_ + len, false);

  if (impl_->blobCapacity_ - impl_->writeOffset_ < len) {
    Blob& b = impl_->BlobAt(impl_->writingBlob_ - 1);
    b.setSize(impl_->writeOffset_);
    impl_->writingBlob_ += 1;
    impl_->writeOffset_ = 0;
  }

  Blob& b = impl_->BlobAt(impl_->writingBlob_ - 1);
  memcpy(const_cast<char*>(b.content()) + impl_->writeOffset_, data, len);
  impl_->writeIndex_ += len;
  impl_->writeOffset_ += len;
  if (impl_->writeOffset_ > b.size()) {
    b.setSize(impl_->writeOffset_);
  }
}

void MemoryBuffer::PrependBytes( const char* data, uint32 len ) {
  //todo: to be implemented
}

void MemoryBuffer::PrependValue( const char* data, uint32 len ) {
  //todo: to be implemented
}

Result MemoryBuffer::ReadBytes( uint32 len, char* data ) {
  impl_->InternalCheck();

  if (Size() < len) {
		return Result(kRunOutOfData, 
      _T("data size is shorter than request length, read bytes failed"), 10014);
  }

  uint32 dataOffset = 0;
  while (dataOffset < len && impl_->readingBlob_ <= impl_->blobs_.size()) {
    Blob& b = impl_->BlobAt(impl_->readingBlob_ - 1);
    if (b.size() <= impl_->readOffset_) {
      impl_->readingBlob_ += 1;
      impl_->readOffset_ = 0;
      continue;
    }

    uint32 readSize = std::min(b.size() - impl_->readOffset_, len - dataOffset);
    memcpy(data + dataOffset, b.content() + impl_->readOffset_, readSize);
    impl_->readOffset_ += readSize;
    impl_->readIndex_ += readSize;
    dataOffset += readSize;
  }

  return Result();
}

Result MemoryBuffer::ReadBytes(uint32 len, MemoryBuffer& buf) {
  if (len == 0) {
    _LWARNING() << "MemoryBuffer::ReadBytes->read length is zero";
    return Result();
  }

  impl_->InternalCheck();

  if (Size() < len) {
    return Result(kRunOutOfData, 
      _T("data size is shorter than request length, read bytes failed"), 10014);
  }

  uint32 dataOffset = 0;
  while (dataOffset < len && impl_->readingBlob_ <= impl_->blobs_.size()) {
    Blob& b = impl_->BlobAt(impl_->readingBlob_ - 1);
    if (b.size() <= impl_->readOffset_) {
      impl_->readingBlob_ += 1;
      impl_->readOffset_ = 0;
      continue;
    }

    uint32 readSize = std::min(b.size() - impl_->readOffset_, len - dataOffset);
    buf.WriteBytes(b.content() + impl_->readOffset_, readSize);
    impl_->readOffset_ += readSize;
    impl_->readIndex_ += readSize;
    dataOffset += readSize;
  }

  return Result();
}

Result MemoryBuffer::PeekBytes(uint32 len, char* data) {
  impl_->InternalCheck();

  if (Size() < len) {
    return Result(kRunOutOfData, _T("data size is shorter than request length, read bytes failed"), 10014);
  }

  uint32 readingBlob = impl_->readingBlob_;
  uint32 readOffset = impl_->readOffset_;
  uint32 readIndex = impl_->readIndex_;

  uint32 dataOffset = 0;
  while (dataOffset < len && readingBlob <= impl_->blobs_.size()) {
    Blob& b = impl_->BlobAt(readingBlob - 1);
    if (b.size() <= readOffset) {
      readingBlob += 1;
      readOffset = 0;
      continue;
    }

    uint32 readSize = std::min(b.size() - readOffset, len - dataOffset);
    memcpy(data + dataOffset, b.content() + readOffset, readSize);
    readOffset += readSize;
    readIndex += readSize;
    dataOffset += readSize;
  }

  return Result();
}

Blob MemoryBuffer::ReadBytes( uint32 len ) {
  if (len <= 0) {
    return Blob();
  }

  impl_->InternalCheck();

  if (Size() < len) {
    return Blob();
  }

  Blob::buffer_ptr buffer = boost::allocate_shared<char[]>(impl_->allocator_, len);

  uint32 dataOffset = 0;
  while (dataOffset < len && impl_->readingBlob_ <= impl_->blobs_.size()) {
    Blob& b = impl_->BlobAt(impl_->readingBlob_ - 1);
    if (b.size() < impl_->readOffset_) {
      impl_->readingBlob_ += 1;
      impl_->readOffset_ = 0;
      continue;
    }

    uint32 readSize = std::min(b.size() - impl_->readOffset_, len);
    memcpy(buffer.get() + dataOffset, b.content() + impl_->readOffset_, readSize);
    impl_->readOffset_ += readSize;
    impl_->readIndex_ += readSize;
    dataOffset+= readSize;
  }

  return Blob(buffer, len);
}

tuple<const char*, uint32> MemoryBuffer::ReadBytes() {
  impl_->InternalCheck();

  if (impl_->InternalSize() > 0 && impl_->readingBlob_ <= impl_->writingBlob_) {
    Blob& b = impl_->BlobAt(impl_->readingBlob_ - 1);
    if (b.size() == impl_->readOffset_) {
      if (impl_->readingBlob_ < impl_->writingBlob_) {
        impl_->readingBlob_++;
        impl_->readOffset_ = 0;
        b = impl_->BlobAt(impl_->readingBlob_ - 1);
      } else {
        return make_tuple(static_cast<const char*>(nullptr), 0);
      }
    }

    uint32 offset = impl_->readOffset_;
    uint32 readSize = std::min(Size(), b.size() - impl_->readOffset_);
    impl_->readOffset_ = b.size();
    impl_->readIndex_ += readSize;
    return make_tuple(b.content() + offset, readSize);
  }

  return make_tuple(static_cast<const char*>(nullptr), 0);
}

void MemoryBuffer::Reset() {
	impl_->readOffset_ = 0;
	impl_->writeOffset_ = 0;
	impl_->readIndex_ = 0;
	impl_->writeIndex_ = 0;
	impl_->readingBlob_ = 1;
	impl_->writingBlob_ = 0;
}

uint32 MemoryBuffer::BlobCapacity() const {
	return impl_->blobCapacity_;
}

TB_NAMESPACE_END